{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению. Сессия № 3\n",
    "\n",
    "### <center> Автор материала: Демо Владимир Олегович"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## <center> Индивидуальный проект по анализу данных: </center>\n",
    "## <center> Прогноз успешного выступления на соревнованиях по Пауэрлифтингу </center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "## подключаемые библиотеки\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn import preprocessing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  1. Описание набора данных и признаков (2 балла)\n",
    "\n",
    "Описан процесс сбора данных (если применимо), есть подробное описание решаемой задачи, в чем ее ценность, дано описание целевого и прочих признаков;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Процесс сбора данных:__\n",
    "\n",
    "Данные для анализа взяты с Каггл:\n",
    "https://www.kaggle.com/open-powerlifting/powerlifting-database\n",
    "\n",
    "Скачать их можно по ссылке:\n",
    "https://www.kaggle.com/open-powerlifting/powerlifting-database/downloads/powerlifting-database.zip/1\n",
    "\n",
    "__Подробное описание решаемой задачи:__\n",
    "\n",
    "Пауэрли́фтинг (англ. powerlifting; power — «сила, мощь» + lifting — «поднятие») или силовое троеборье — силовой вид спорта, суть которого заключается в преодолении сопротивления максимально тяжёлого для спортсмена веса.\n",
    "\n",
    "Пауэрлифтинг также называют силовым троеборьем. Связано это с тем, что в качестве соревновательных дисциплин в него входят три упражнения (приседания со штангой на спине (точнее на верхней части лопаток), жим штанги лежа на горизонтальной скамье и тяга штанги), которые в сумме определяют квалификацию спортсмена.\n",
    "\n",
    "На основе данных сайта \"Openpowerlifting.org\" постараемся дать ответ на следующие вопросы:\n",
    "    1. Влияет ли категория, в которой выступает пауэрлифтер на результаты соревнований?\n",
    "    2. Как экипировка влияет на достижение результата?\n",
    "    3. Можно ли спрогнозировать победу чемпионата на основе первых 2 испытаний (присед и жим)?\n",
    "\n",
    "__Ценность задачи:__\n",
    "    У некоторых начинающих спортсменов результаты по отдельным дисциплинам в среднем лучше, чем по другим. Следовательно, необходимо понять, наксколько велик шанс спортсмена достигнуть призового места.\n",
    "\n",
    "__Описание целевого и прочих признаков:__\n",
    "\n",
    "Целевой признак:\n",
    "- Place - место в соревнованиях\n",
    "\n",
    "Прочие признаки:\n",
    "- MeetID - ID проведенного мероприятия;\n",
    "- Name - имя спортсмена (Имя Фамилия на англ. языке);\n",
    "- Sex - пол спортсмена (male - мужской /female - женский);\n",
    "- Equipment - экипировка (Multi-ply - многослойная, Raw - без экипировки, Single-ply - однослойная, Straps - бинты на кисти рук, Straps - бинты на колени ног);\n",
    "- Age - возраст спортсмена в полных годах;\n",
    "- Division - дивизион в котором выступает спортсмен (возрастная, проверка на допинг и пр.);\n",
    "- BodyweightKg - вес спортсмена (кг);\n",
    "- WeightClassKg - весовая категория спортсмена (его вес меньше или равен данной категории, кг);\n",
    "- BestSquatKg - лучший результат в дисциплине \"Присед со штангой\" (кг);\n",
    "- BestBenchKg - лучший результат в дисциплине \"Жим штанги лежа\" (кг);\n",
    "- BestDeadliftKg - лучший результат в дисциплине \"Становая тяга штанги\" (кг);\n",
    "- TotalKg - сумма трех испытаний (кг);\n",
    "- Wilks - коэффициент Вилкса, который позволяет на соревнованиях определить абсолютного чемпионата вне зависимости от весовой категории."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op = pd.read_csv('../../data/openpowerlifting.csv')\n",
    "op.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  2. Первичный анализ признаков\n",
    "\n",
    "Исследованы признаки, их взаимодействия, влияние на целевой признак. Исследовано распределение целевого признака (в случае задачи регрессии проведены стат-тесты на нормальность и скошенность (skewness) распределения). Если необходимо, объясняется, почему и как можно преобразовать целевой признак. Изучены выбросы и пропуски в данных;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(op.info())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Предобработка данных (4 балла)\n",
    "\n",
    "Проведена предобработка данных для конкретной модели. При необходимости есть и описано масштабирование признаков, заполнение пропусков, замены строк на числа, OheHotEncoding, обработка выбросов, отбор признаков с описанием используемых для этого методов. Корректно сделано разбиение данных на обучающую и отложенную части;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим данные на наличие пропусков. Определим долю пропусков от общего числа данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op.isnull().sum() / op.shape[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Столцы Squat4Kg, Bench4Kg, Deadlift4Kg в большинстве отсутствуют (>99%) и age в 62% случаев, следовательно данные признаки удаляем из dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op=op.drop([\"Squat4Kg\", \"Bench4Kg\", \"Deadlift4Kg\", \"Age\"], axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Также имеем в столбцах BestSquatKg,\tBestBenchKg, BestDeadliftKg отрицательные значения, что по физической природе является выбросом. Спишем это на ошибки ввода, следовательно данные параметры необходимо взять по модулю"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op['BestSquatKg']=np.abs(op['BestSquatKg'])\n",
    "op['BestBenchKg']=np.abs(op['BestBenchKg'])\n",
    "op['BestDeadliftKg']=np.abs(op['BestDeadliftKg'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Заменим все NaN в столбцах BestSquatKg, BestBenchKg, BestDeadliftKg, TotalKg, Wilks на \"нули\" (спортсмену не удалось выполнить начальный вес в состязаниях, поэтому у него суммарный вес и Вилкс тоже \"ноль\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op['BestSquatKg']=op['BestSquatKg'].fillna(0)\n",
    "op['BestBenchKg']=op['BestBenchKg'].fillna(0)\n",
    "op['BestDeadliftKg']=op['BestDeadliftKg'].fillna(0)\n",
    "op['TotalKg']=op['TotalKg'].fillna(0)\n",
    "op['Wilks']=op['Wilks'].fillna(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим также на корректность итоговую сумму трех дисциплин. Создадим отдельную колонку, где покажем процент ошибки ввода данных. Затем удалим строчки, где ошибка больше 1%"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op['TotalKg_check']=(op['TotalKg']-(op['BestSquatKg']+op['BestBenchKg']+op['BestDeadliftKg']))/op['TotalKg']\n",
    "op=op.drop(op[np.abs(op['TotalKg_check'])>0.01].index)\n",
    "\n",
    "op=op.drop(['TotalKg_check'], axis=1)\n",
    "op.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Заметим, что еще остались значения Nan в признаке \"BodyweightKg\", следовательно удалим все строчки, где данный признак не указан, чтобы количество элементов в каждом признаке было одинаковым."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op=op.drop(op[op[\"BodyweightKg\"].isnull()].index)\n",
    "op.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Получили \"очищенные данные\", которые демонстрируют статистику по итоговым места в соревнованиях из 370219 строчек"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op.groupby('Place')\n",
    "place_stat=op['Place'].value_counts()\n",
    "place_stat[0:10]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op['Place'].hist(bins=25)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\"Место\" в соревнованиях определим как целевой признак. Чтобы в дальнейшем можно было провести прогноз, необходимо выполнить еще несколько подготовительных пунктов:\n",
    "- создание новых признаков\n",
    "- перевести текстовые значения в числовые для возможностей анализа и прогноза"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. Создание новых признаков и описание этого процесса (4 балла)\n",
    "\n",
    "Созданы новые признаки. Дано обоснование: логическое (например, у птиц температура тела на несколько градусов выше человеческой, значит вирус ХХХ не выживет в такой среде), физическое (например, радуга означает, что источник света расположен сзади; расчет величины по физическому закону с использованием данных признаков) или другое (скажем, признак построен после визуализации данных). Обоснование разумно описано. Полезность новых признаков подтверждена статистически или с помощью соответствующей модели;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создаем новый признак: \"Число участий в соревнований\" - 'MeetCount'. Каждый спортсмен будет иметь данные по сумме соревнований, что в дальнейшем позволяет понять насколько он опытный."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op2=op.drop(['Sex','Equipment','Division','BodyweightKg','WeightClassKg','BestSquatKg','BestBenchKg','BestDeadliftKg','TotalKg','Wilks','Place'], axis=1)\n",
    "op3=op2.groupby('Name').agg(np.count_nonzero)\n",
    "op4=op3.rename(columns={'MeetID': 'MeetCount'})\n",
    "op4['Name']=op4.index\n",
    "op = op.merge(op4, 'left', on='Name')\n",
    "op.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В дальнейшем признак 'Name' нам не понадобится, поэтому его удаляем."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op=op.drop(['Name'], axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Переводим категориальные признаки в числовые"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op['Division'] = pd.factorize(op.Division)[0]\n",
    "op['Equipment'] = pd.factorize(op.Equipment)[0]\n",
    "op['Sex'] = pd.factorize(op.Sex)[0]\n",
    "op['WeightClassKg'] = pd.factorize(op.Place)[0]\n",
    "op['Place'] = pd.factorize(op.Place)[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Признак \"place\" имеет признак: \"место\" в числовом значении и \"дисквалификация\" в буквенном. Нас не интересует не 1 место, следовательно \"place\" который не равен 1 (после факторризации 0) будет равен 0, а остальное 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op['Place'][op['Place']==0]=0\n",
    "op['Place'][op['Place']!=0]=1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Первичный визуальный анализ данных (4 балла)\n",
    "\n",
    "Построены визуализации (распределения признаков, матрица корреляций и т.д.), описана связь с анализом данным (п. 2). Присутствуют выводы;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.heatmap(op.corr())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "corr = op.corr()\n",
    "corr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Инсайты, найденные зависимости (4 балла)\n",
    "\n",
    "Найдены и выдвинуты предположения о природе различных корреляций/пропусков/закономерностей и выбросов, найденных в предыдущих пунктах. Есть пояснение, почему они важны для решаемой задачи;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Насколько видим из матрицы корреляции, место ('Place' - целевой признак) коррелирует лучше всего с WeightClassKg (весовая категория, в которой выступает спортсмен). Чем выше категория, тем меньше вероятность победы.\n",
    "\n",
    "С экипировкой никакой корреляции нет.\n",
    "\n",
    "Для прогнозы победы на основе двух первых испытаний (присед и жим) необходимо удалить данные по становой тяге, суммарный взятый вес и Вилкс, так как они являются результатом соревнований и напрямую прогнозируют победу."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "op=op.drop(['BestDeadliftKg', 'TotalKg','Wilks'], axis=1)\n",
    "op.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Выбор метрики (3 балла)\n",
    "\n",
    "Есть разумное обоснование выбора метрики качества модели. Описаны моменты, влияющие на выбор метрики качества (решаемая задача, цель решения, количество классов, дисбаланс классов, прочее);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "target = op['Place']\n",
    "train = op.drop(['Place'], axis=1) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.tree import DecisionTreeClassifier\n",
    "from sklearn.model_selection import GridSearchCV, StratifiedKFold\n",
    "from sklearn.cross_validation import cross_val_score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dt = DecisionTreeClassifier(random_state=17, class_weight='balanced')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "max_depth_values = range(1, 10)\n",
    "max_features_values = range(1, 10)\n",
    "tree_params = {'max_depth': max_depth_values,\n",
    "               'max_features': max_features_values}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=17)\n",
    "from sklearn.metrics import accuracy_score\n",
    "X = train\n",
    "y = target"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid=GridSearchCV(dt,tree_params,cv=skf, n_jobs=-1,scoring='roc_auc',verbose=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid.fit(X, y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.ensemble import RandomForestClassifier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dt2 = RandomForestClassifier(n_jobs=1, random_state=17)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "max_depth_values = range(1, 10)\n",
    "max_features_values = range(1, 10)\n",
    "tree_params = {'max_depth': max_depth_values,\n",
    "               'max_features': max_features_values}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid2=GridSearchCV(dt2,tree_params,cv=skf, n_jobs=-1,scoring='roc_auc',verbose=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid2.fit(X, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Кросс-валидация и настройка гиперпараметров модели (4 балла)\n",
    "\n",
    "Кросс-валидация выполнена технически верно, нет утечек данных. Разумно выбрано количество фолдов и разбиение (Random/Stratified или иное), зафиксирован seed. Присутствует объяснение. Объяснены гиперпараметры модели и способ их выбора. Выбор основан на некотором исследовании гипрепараметров модели для данной задачи;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "clf_best_score = grid.best_score_\n",
    "clf_best_params = grid.best_params_\n",
    "clf_best = grid.best_estimator_\n",
    "mean_validation_scores = []\n",
    "print(\"Лучший результат в дереве решений\", clf_best_score, \n",
    "      \"лучшие параметры\", clf_best_params)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "clf_best_score2 = grid2.best_score_\n",
    "clf_best_params2 = grid2.best_params_\n",
    "clf_best2 = grid2.best_estimator_\n",
    "print(\"Лучший результат в случайном лесе\", clf_best_score2, \n",
    "      \"лучшие параметры\", clf_best_params2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Выбор модели (3 балла)\n",
    "\n",
    "Произведен выбор модели. Описан процесс выбора и связь с решаемой задачей;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видно, \"случайный лес\" и \"дерево решений\" дают точный прогноз (100%) победы спортсмена"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. Прогноз для тестовой или отложенной выборке (2 балла)\n",
    "\n",
    "Указаны результаты на тестовой выборке или LB score. Результаты на тестовой выборке сравнимы с результатами на кросс-валидации. Если тестовая выборка создавалась автором проекта, то механизм создания должен быть непредвзят и объяснен (применен разумный механизм выборки, в простейшем случае – рандомизация);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выполним прогноз при помощи \"случайного леса\" с лучшими параметрами на тренировочной и отложенной выборке"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "X_train, X_valid, y_train, y_valid = train_test_split(train,target,test_size=0.3, random_state=17)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dt3 = RandomForestClassifier(n_jobs=1, max_depth=2, max_features=6,random_state=17)\n",
    "dt3.fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dt3_pred = dt3.predict(X_valid)\n",
    "accuracy_score(y_valid, dt3_pred)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dt4 = DecisionTreeClassifier(class_weight='balanced',max_depth=2, max_features=5,random_state=17)\n",
    "dt4.fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dt4_pred = dt4.predict(X_valid)\n",
    "accuracy_score(y_valid, dt4_pred)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В итоге, \"дерево решений\" отлично справляется с прогнозом победы на соревнованиях по Пауэрлифтингу по результатам отложенной выборки (100%), в отличие от \"случайного леса\", где прогноз хуже (99.7%)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12. Выводы (2 балла)\n",
    "\n",
    "Описана ценность решения, возможности применения, дальнейшие пути развития и улучшения решения;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Прогноз победы на соревнованиях по Пауэрлифтингу имеет низкую ценность, так как на такого рода соревнованиях отсутствует тотализатор. При этом на основе первичных данных о спортсмене, его опыт, экипировка, весовая категория и результаты по первым двум испытаниям есть возможность спрогнозировать победу со 100% вероятностью."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
